﻿Array.prototype.select = Array.prototype.select || function (func)
{
    var arr = [];
    for (var i = 0; i < this.length; i++)
        arr.push(func ? func(this[i], i) : this[i]);
    return arr;
}
Array.prototype.forAll = Array.prototype.forAll || function (func)
{
    for (var i = 0; i < this.length; i++)
        if (func)
            func(this[i], i);
}
Array.prototype.distinct = Array.prototype.distinct || function ()
{
    var u = {}, a = [];
    for (var i = 0, l = this.length; i < l; ++i)
    {
        if (u.hasOwnProperty(this[i])) continue;
        a.push(this[i]);
        u[this[i]] = 1;
    }
    return a;
}
Array.prototype.where = Array.prototype.where || function (func)
{
    var arr = [];
    for (var i = 0; i < this.length; i++)
        if (!func || func(this[i], i))
            arr.push(this[i]);
    return arr;
}
Array.prototype.all = Array.prototype.all || function (func) {
    var result = this.where(func).length === this.length;
    return result;
}
Array.prototype.any = Array.prototype.any || function (func)
{        
    return this.firstOrDefault(func) != null;
}
Array.prototype.accumulate = Array.prototype.accumulate || function (initial, func) // initial parameter is required
{
    var accum = initial;
    for (var i = 0; i < this.length; i++)
        accum = func ? func(accum, this[i]) : (accum + this[i]);
    return accum;
}
Array.prototype.firstOrDefault = Array.prototype.firstOrDefault || function (predicate)
{
    if (!predicate)
        return this.length > 0 ? this[0] : null;

    for (var i = 0; i < this.length; i++)
        if (predicate(this[i]))
            return this[i];

    return null;
}
Array.prototype.first = Array.prototype.first || function (predicate)
{
    var firstOrDefault = this.firstOrDefault(predicate);
    if (firstOrDefault == null)
        throw "First item not found";
    else
        return firstOrDefault;
}
Array.prototype.lastOrDefault = Array.prototype.lastOrDefault || function (predicate)
{
    return this.reverse.firstOrDefault(predicate);
}
Array.prototype.last = Array.prototype.last || function (predicate)
{
    return this.reverse.first(predicate);
}

Array.prototype.union = Array.prototype.union || function(array)
{
    var arr = [];

    for (var i = 0; i < this.length; i++)
        arr.push(this[i]);

    if (IsArray(array))
        for (var k = 0; k < array.length; k++)
            arr.push(array[k]);

    return arr;
}

Array.prototype.orderBy = Array.prototype.orderBy || function (func)
{
    return this.select().sort(function (a, b)
    {
        var objA = func? func(a): a;
        var objB = func? func(b): b;

        var comparison = null;
        if (objA && objB)
        {
	        if (typeof(objA) === "number")
		        comparison = objA - objB;
	        else if (IsDate(objA))
		        comparison = objA.getTime() - objB.getTime();
	        else
		        comparison = objA.toString().localeCompare(objB.toString());
        }
        else
        {
	        if (!objA && !objB)
	        	comparison = 0;
	        else if (!objA)
		        comparison = -1;
	        else
		        comparison = 1;
        }
	    return comparison;
    });
}
Array.prototype.orderByDesc = Array.prototype.orderByDesc || function (func)
{
    return this.orderBy(func).reverse();
}

Array.prototype.groupBy = Array.prototype.groupBy || function (func)
{
    var groups = new Dictionary();
    for (var i = 0; i < this.length; i++)
    {
        var key = func(this[i]);        
        if (!groups.hasKey(key))
            groups.put(key, []);
        groups.get(key).push(this[i]);
    }
    return groups;
}
Array.prototype.aggregate = Array.prototype.aggregate || function(key, value, aggregation)
{
    // if key is passed as generator function, use it, else create generator function that assumes key is a property name. 
    // If key is null/missing, the string representation of an object is used as the key. 
    var keyGenerator = IsFunction(key) ? key : function (datum) { return key ? datum[key.toString()] : datum.toString(); };

    //Same for value
    var valueGenerator = IsFunction(value) ? value : function (datum) { return value ? datum[value.toString()] : datum.toString(); };

    return this
        .groupBy(keyGenerator)
        .select(function(groupData) {
            var aggProjector = function(datum) { return valueGenerator(datum); };
            switch (aggregation) {
            case "count":
                return groupData.count(aggProjector);

            case "sum":
                return groupData.sum(aggProjector);

            case "avg":
                return groupData.avg(aggProjector);

            case "max":
                return groupData.max(aggProjector);

            case "min":
                return groupData.min(aggProjector);
            default:
                return null;
            }
        });
}

Array.prototype.sum = Array.prototype.sum || function (func)
{
    var sum = 0;
    for (var i in this)
        if (this.hasOwnProperty(i))
            sum += (func ? func(this[i]) : 0);
    return sum;
}
Array.prototype.count = Array.prototype.count || function (func)
{
    var count = 0;
    for (var i in this)
        if (this.hasOwnProperty(i) && (func ? func(this[i]) : this[i]))
            count++;
    return count;
}
Array.prototype.avg = Array.prototype.avg || function (func)
{
    return this.sum(func) / this.count();
}
Array.prototype.max = Array.prototype.max || function (func)
{
    var max = Number.NEGATIVE_INFINITY;
    for (var i in this)
        if (this.hasOwnProperty(i))
        {
            var value = (func ? func(this[i]) : this[i]);
            if (value > max)
                max = value;
        }
    return max;
}
Array.prototype.min = Array.prototype.min || function (func)
{
    var min = Number.POSITIVE_INFINITY;
    for (var i in this)
        if (this.hasOwnProperty(i))
        {
            var value = (func ? func(this[i]) : this[i]);
            if (value < max)
                min = value;
        }
    return min;
}

Array.prototype.removeItem = Array.prototype.removeItem || function (item)
{
    var arr = [];
    for (var i = 0; i < this.length; i++)
        if (this[i] != item)
            arr.push(this[i]);
    return arr;
};
Array.prototype.removeAt = Array.prototype.removeAt || function (index)
{
    var arr = [];
    for (var i = 0; i < this.length; i++)
        if (i != index)
            arr.push(this[i]);
    return arr;
};
Array.prototype.insert = Array.prototype.insert || function (index, item)
{
    this.splice(index, 0, item);
};

